//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2006-2007
//
// List of cities.
//

# include "StdAfx.h"
# include "Resource.h"
# include "CitiesList.h"
# include "PAItems.h"
# include "Dialog.h"
# include "EditText.h"
# include "ChangePAI.h"
# include "ShowRgnOnMap.h"
# include "Globals.h"

namespace {
	struct LessCity {
		LessCity (bool _bAscending, cities_t::iterator _iNoCity) : m_bAscending (_bAscending), m_iNoCity (_iNoCity) {}

		bool operator () (const cities_t::iterator & _ci1, const cities_t::iterator & _ci2) {
			if (_ci1 == m_iNoCity)
				return true;
			if (_ci2 == m_iNoCity)
				return false;
			const int iCmp = ::lstrcmp (_ci1->strName.c_str (), _ci2->strName.c_str ());
			return m_bAscending ? iCmp < 0 : iCmp > 0;
		}

	  private:
		const bool m_bAscending;
		const cities_t::iterator m_iNoCity;
	};

	struct LessRegion {
		LessRegion (bool _bAscending, cities_t::iterator _iNoCity) : m_bAscending (_bAscending), m_iNoCity (_iNoCity) {}

		bool operator () (const cities_t::iterator & _ci1, const cities_t::iterator & _ci2) {
			if (_ci1 == m_iNoCity)
				return true;
			if (_ci2 == m_iNoCity)
				return false;
			const int iCmp = ::lstrcmp (_ci1->pRegion->strName.c_str (), _ci2->pRegion->strName.c_str ());
			return m_bAscending ? iCmp < 0 : iCmp > 0;
		}

	  private:
		const bool m_bAscending;
		const cities_t::iterator m_iNoCity;
	};

	struct LessCountry {
		LessCountry (bool _bAscending, cities_t::iterator _iNoCity) : m_bAscending (_bAscending), m_iNoCity (_iNoCity) {}

		bool operator () (const cities_t::iterator & _ci1, const cities_t::iterator & _ci2) {
			if (_ci1 == m_iNoCity)
				return true;
			if (_ci2 == m_iNoCity)
				return false;
			const int iCmp = ::lstrcmp (_ci1->pRegion->pCountry->strName.c_str (), _ci2->pRegion->pCountry->strName.c_str ());
			return m_bAscending ? iCmp < 0 : iCmp > 0;
		}

	  private:
		const bool m_bAscending;
		const cities_t::iterator m_iNoCity;
	};

	struct LessObjects {
		LessObjects (bool _bAscending, cities_t::iterator _iNoCity) : m_bAscending (_bAscending), m_iNoCity (_iNoCity) {}

		bool operator () (const cities_t::iterator & _ci1, const cities_t::iterator & _ci2) {
			if (_ci1 == m_iNoCity)
				return true;
			if (_ci2 == m_iNoCity)
				return false;
			return m_bAscending ? _ci1->cObjects < _ci2->cObjects : _ci1->cObjects > _ci2->cObjects;
		}

	  private:
		const bool m_bAscending;
		const cities_t::iterator m_iNoCity;
	};
}

enum {
	clCity    = 0,
	clRegion  = 1,
	clCountry = 2,
	clObjects = 3
};

//////////////////////////////////////////////////////////////

void CCitiesList::Init (HWND _hwndParent, WORD _wID) {
	m_List.Init (_hwndParent, IDC_LIST, "Cities");
	m_List.SetExtStyle (LVS_EX_FULLROWSELECT);

	AddColumn (m_List, clCity,    "City");
	AddColumn (m_List, clRegion,  "Region/Province/State");
	AddColumn (m_List, clCountry, "Country");
	if (m_bShowCount)
		AddColumn (m_List, clObjects, "Objects");
}

void CCitiesList::Destroy () {
	m_List.Destroy ();
}

void CCitiesList::_PrepareCities () {
	CCritSec::CLock lock (g_cs);

	m_vecCities.clear ();
	m_vecCities.reserve (g_map.Cities.size () + (m_bShowNoCity ? 1 : 0));

	if (m_bShowNoCity) {
		m_iNoCity = g_map.Cities.end ();
		m_vecCities.push_back (m_iNoCity);
	}

	for (cities_t::iterator i = g_map.Cities.begin (); i != g_map.Cities.end (); ++ i) {
		city_t & city = * i;
		if (city.IsDeleted ())
			continue;

		city.cObjects   = 0;
		city.bLocatable = false;

		m_vecCities.push_back (i);
	}
	
	// Update statistics.
	if (m_bShowCount) {
		for (int nKind = 0; nKind < rkMax; ++ nKind) {
			const layer_t & points = g_map.layers [nKind];
			for (rgns_t::const_iterator i = points.rgns.begin (); i != points.rgns.end (); ++ i) {
				const rgn_t & rgn = * i;
				if (rgn.pExtra && rgn.pExtra->pCity) {
					++ rgn.pExtra->pCity->cObjects;

					if (
						(rgn.dwFlags & rgn_t::fIndexedCity) &&
						::CompareString (
							LOCALE_USER_DEFAULT, NORM_IGNORECASE,
							rgn.pExtra->pCity->strName.c_str (), rgn.pExtra->pCity->strName.size (),
							rgn.strLabel              .c_str (), rgn.strLabel              .size ()
						) == CSTR_EQUAL
					)
						rgn.pExtra->pCity->bLocatable = true;
				}
			}
		}
	}
}

void CCitiesList::_FillCities (bool _bSelect, const city_t * _pCity) {
	// Find index of city to select.
	size_t cSelItem = -1;
	const size_t cCities = m_vecCities.size ();
	if (_bSelect) {
		for (size_t c = 0; c < cCities; ++ c) {
			const cities_t::iterator ci = m_vecCities [c];

			if (m_bShowNoCity && ci == m_iNoCity) {
				if (_pCity == NULL) {
					cSelItem = c;
					break;
				}
			} else {
				const city_t & city = * ci;
				if (& city == _pCity) {
					cSelItem = c;
					break;
				}
			}
		}
	}

	// Update list view.
	ListView_SetItemCount (m_List, cCities);

	// Select city.
	if (_bSelect && cSelItem != -1) {
		SetSelection           (m_List, cSelItem);
		ListView_EnsureVisible (m_List, cSelItem, FALSE);
	}
}

size_t CCitiesList::GetCities () const {
	return m_vecCities.size ();
}

void CCitiesList::_SortCities () {
	if (! m_bShowNoCity)
		// Have m_iNoCity always initialized.
		m_iNoCity = g_map.Cities.end ();

	switch (m_List.GetSortingColumn ()) {
		case -1:
			break;

		case clCity:    std::sort (m_vecCities.begin (), m_vecCities.end (), LessCity    (m_List.IsSortingAscending (), m_iNoCity)); break;
		case clRegion:  std::sort (m_vecCities.begin (), m_vecCities.end (), LessRegion  (m_List.IsSortingAscending (), m_iNoCity)); break;
		case clCountry: std::sort (m_vecCities.begin (), m_vecCities.end (), LessCountry (m_List.IsSortingAscending (), m_iNoCity)); break;
		case clObjects:
			if (m_bShowCount)
				std::sort (m_vecCities.begin (), m_vecCities.end (), LessObjects (m_List.IsSortingAscending (), m_iNoCity));
			break;

		default:
			assert (false);
			return;
	}
}

void CCitiesList::Activate (bool _bSelect, const city_t * _pCity) {
	_PrepareCities ();

	const size_t cCities = g_map.Cities.size ();
	if (cCities > 20000)
		// NOTE: avoid first long sorting.
		m_List.SetSortingColumn (-1);
	_SortCities ();

	_FillCities (_bSelect, _pCity);

	// NOTE: this does not sort virtual list view indeed, but it shows sorting arrows in the header.
	m_List.Sort ();

	// Set the best width.
	AutoSizeColumns (m_List);
}

void CCitiesList::InsertLastCity () {
	if (! g_map.Cities.empty ()) {
		cities_t::iterator iLast = g_map.Cities.end ();
		-- iLast;
		m_vecCities.push_back (iLast);

		_FillCities (true, & g_map.Cities.back ());
	}
}

void CCitiesList::EditSelCity () {
	const size_t cSel = ListView_GetSelectionMark (m_List);
	if (cSel == -1)
		return;

	assert (cSel < m_vecCities.size ());
	if (cSel >= m_vecCities.size ())
		return;
	const cities_t::iterator ci = m_vecCities [cSel];
	if (m_bShowNoCity && ci == m_iNoCity)
		return;
	city_t * const pCity = & * ci;

	CCreateOrEditCity cecd (pCity);
	if (cecd.ShowModal (m_List) == 0)
		_FillCities (true, pCity);
}

bool CCitiesList::RemoveSelCity () {
	const size_t cSel = ListView_GetSelectionMark (m_List);
	if (cSel == -1)
		return false;

	assert (cSel < m_vecCities.size ());
	if (cSel >= m_vecCities.size ())
		return false;
	const cities_t::iterator ci = m_vecCities [cSel];
	if (m_bShowNoCity && ci == m_iNoCity)
		return false;
	const city_t * const pCityNext = cSel + 1 < m_vecCities.size () ? & * m_vecCities [cSel + 1] : NULL;

	m_vecCities.erase (m_vecCities.begin () + cSel);

	DeletePAI (& * ci);
	_FillCities (true, pCityNext);

	return pCityNext != NULL;
}

void CCitiesList::LocateSelCity () {
	const size_t cSel = ListView_GetSelectionMark (m_List);
	if (cSel == -1)
		return;

	assert (cSel < m_vecCities.size ());
	if (cSel >= m_vecCities.size ())
		return;
	const cities_t::iterator ci = m_vecCities [cSel];
	if (m_bShowNoCity && ci == m_iNoCity)
		return;
	const city_t * const pCity = & * ci;

	CCritSec::CLock lock (g_cs);

	const layer_t & points = g_map.layers [rkPoint];
	for (rgns_t::const_iterator i = points.rgns.begin (); i != points.rgns.end (); ++ i) {
		const rgn_t & rgn = * i;
		if ((rgn.dwFlags & rgn_t::fIndexedCity) && rgn.pExtra && rgn.pExtra->pCity == pCity) {
			if (rgn.origins.empty ())
				break;

			ShowRgnOnMap (rgn);
				
			return;
		}
	}

	ErrorMsgBox ("City is not found.");
}

bool CCitiesList::IsCityLocatable (size_t _cIndex) {
	assert (_cIndex < m_vecCities.size ());
	if (_cIndex >= m_vecCities.size ())
		return false;
	const cities_t::iterator ci = m_vecCities [_cIndex];
	if (m_bShowNoCity && ci == m_iNoCity)
		return false;
	return ci->bLocatable;
}

city_t * CCitiesList::GetSelCity () const {
	const size_t cSel = ListView_GetSelectionMark (m_List);
	if (cSel == -1)
		return NULL;

	assert (cSel < m_vecCities.size ());
	if (cSel >= m_vecCities.size ())
		return NULL;
	const cities_t::iterator ci = m_vecCities [cSel];
	if (m_bShowNoCity && ci == m_iNoCity)
		return NULL;

	return & * ci;
}

bool CCitiesList::OnParentNotify (int _idCtrl, NMHDR * _pInfo, CDialogBase * _pDialog) {
	switch (_pInfo->code) {
		case LVN_GETDISPINFO: {
			NMLVDISPINFO * const pInfo2 = reinterpret_cast<NMLVDISPINFO *> (_pInfo);

			assert ((size_t) pInfo2->item.iItem < m_vecCities.size ());
			if ((size_t) pInfo2->item.iItem >= m_vecCities.size ())
				break;
			const cities_t::iterator ci = m_vecCities [pInfo2->item.iItem];

			if (pInfo2->item.mask & LVIF_TEXT) {
				// Fill in the text information.
				if (m_bShowNoCity && ci == m_iNoCity) {
					switch (pInfo2->item.iSubItem) {
						case clCity:    ::_tcsncpy  (pInfo2->item.pszText, "<NO CITY>",  pInfo2->item.cchTextMax); break;
						default:        ::_tcsncpy  (pInfo2->item.pszText, "-",          pInfo2->item.cchTextMax); break;
					}
				} else {
					switch (pInfo2->item.iSubItem) {
						case clCity:    ::_tcsncpy  (pInfo2->item.pszText, OutputPAString (ci->strName),                    pInfo2->item.cchTextMax); break;
						case clRegion:  ::_tcsncpy  (pInfo2->item.pszText, OutputPAString (ci->pRegion->strName),           pInfo2->item.cchTextMax); break;
						case clCountry: ::_tcsncpy  (pInfo2->item.pszText, OutputPAString (ci->pRegion->pCountry->strName), pInfo2->item.cchTextMax); break;
						case clObjects: ::_stprintf (pInfo2->item.pszText, "%d", ci->cObjects); break;
					}
				}
			}
			break;
		}

		case LVN_ODFINDITEM: {
			const NMLVFINDITEM * const pInfo2 = reinterpret_cast <const NMLVFINDITEM *> (_pInfo);
			if ((pInfo2->lvfi.flags & (LVFI_PARTIAL | LVFI_STRING)) == 0)
				break;

			const bool bWrap = (pInfo2->lvfi.flags & LVFI_WRAP) != 0;

			const size_t cPrefixLen = ::_tcslen (pInfo2->lvfi.psz);

			size_t cItemFoundBefore = -1;
			size_t cItemFoundAfter  = -1;
			bool bFoundBefore = false;
			bool bFoundAfter  = false;
			{
				CCritSec::CLock lock (g_cs);

				size_t cItem = 0;
				const size_t cCities = m_vecCities.size ();
				for (size_t c = 0; c < cCities; ++ c) {
					const cities_t::iterator cti = m_vecCities [c];
					const city_t & city = * cti;
					if (city.IsDeleted ())
						continue;

					const bool bBeforeStart = cItem < (size_t) pInfo2->iStart;

					const size_t cCityNameLen = city.strName.size ();
					if (
						cCityNameLen >= cPrefixLen &&
						::CompareString (
							LOCALE_USER_DEFAULT, NORM_IGNORECASE,
							city.strName.c_str (), cPrefixLen,
							pInfo2->lvfi.psz,      cPrefixLen
						) == CSTR_EQUAL
					) {
						if (bBeforeStart) {
							if (bFoundBefore == false)
								cItemFoundBefore = cItem;
							bFoundBefore = true;
						} else {
							if (bFoundAfter == false)
								cItemFoundAfter = cItem;
							bFoundAfter = true;
							break;
						}
					}

					++ cItem;
				}
			}

			if (m_bShowNoCity) {
				if (bFoundAfter)
					++ cItemFoundAfter;
				else
					++ cItemFoundBefore;
			}

			if (bFoundAfter)
				_pDialog->SetMsgResult (cItemFoundAfter);
			else
				_pDialog->SetMsgResult ((bWrap && bFoundBefore) ? cItemFoundBefore : -1);
			return true;
		}

		case NM_RCLICK:
			m_List.PopupMenu_OnHeaderRClick (_pInfo);
			break;

		case LVN_COLUMNCLICK: {
			// Save selection.
			const size_t cSel = ListView_GetSelectionMark (m_List);
			const city_t * pCitySel = NULL;
			if (cSel != -1) {
				assert (cSel < m_vecCities.size ());
				if (cSel < m_vecCities.size ())
					pCitySel = & (* m_vecCities [cSel]);
			}

			m_List.Sort_OnColumnClick (_pInfo);
			_SortCities ();

			_FillCities (true, pCitySel);

			break;
		}

		case NM_RETURN:
		case NM_DBLCLK:
			EditSelCity ();
			break;
	}

	return false;
}
